import { HeartBeatRequest, ConnectedResponse, HeartBeatResponse, RequestMessage, ResponseMessage, LoginRequest, LoginResponse, ConnectedRequest, AnyMessage } from "./SIOMessage";
import { UserEventSource } from "./UserEventSource";
import { StableWebSocket } from "./StableWebSocket";
import { getToken } from "src/utils/auth";


export interface SessionMessage {
    request: HeartBeatRequest | LoginRequest
    response: HeartBeatResponse | LoginResponse | ConnectedResponse
    onRequest: ConnectedRequest
}

export interface SIOSessionEventMap {
    request: RequestMessage
    response: ResponseMessage

    login: {}
    connected: {}
    close: {}
}

export class SIOSession extends UserEventSource<SIOSessionEventMap>{
    public static enableLog: boolean = import.meta.env.DEV;

    public get sid(): string | null { return this._sid }
    public get isLogin(): boolean { return !!this._sid }

    readonly host: string
    private _sid: string | null = null
    private ws: StableWebSocket

    private retryCount = 0

    public constructor(host: string) {
        super()
        this.host = host
        this.ws = new StableWebSocket(this.socketGenerator.bind(this))
        this.ws.beforeReconnect = this.beforeReconnect.bind(this);
        this.ws.onmessage = this.handleMessage.bind(this);
        this.ws.onclose = this.handleClose.bind(this);

        this.on('request', msg => {
            const sessionMsg = msg as SessionMessage['onRequest']
            switch (sessionMsg.action) {
                case 'connected':
                    this.handleConnected(sessionMsg)
                    break
            }
        })
    }
    private socketGenerator(): WebSocket {
        let url = this.host;
        let session = localStorage.getItem('socketio-sid');
        if (session != null) {
            url = url + "?" + session;
        }
        return new WebSocket(url);
    }
    private beforeReconnect(socket: StableWebSocket): Promise<void> {
        let delay = Math.min(60, Math.pow(2, this.retryCount + 1))
        log(`将在${delay}秒后重连`);
        this.retryCount += 1;
        return Promise.delay(delay * 1000);
    }
    public request(action: string,
        body: object): Promise<ResponseMessage> {
        const data: RequestMessage = {
            type: 'request',
            time: (new Date()).getTime(),
            id: getSN().toString(),
            action: action,
            body: body
        };
        this.ws.send(JSON.stringify(data));
        return new Promise<ResponseMessage>((resolve) => {
            this.once('response', resolve, msg => msg.id === data.id)
        })
    }
    protected _request<
        Action extends SessionMessage['request']['action'],
        Body extends Extract<SessionMessage['request'], { action: Action }>['body'],
        Response extends Extract<SessionMessage['response'], { action: Action }>
    >(action: Action,
        body: Body): Promise<Response> {
        return this.request(action, body) as any
    }

    public response(request: RequestMessage, body: object) {
            const data: ResponseMessage = {
                type: 'response',
                time: (new Date()).getTime(),
                action: request.action,
                id: request.id,
                body: body
            };
            this.ws.send(JSON.stringify(data))
    }

    protected _response<
        Request extends SessionMessage['onRequest'],
        Response extends Extract<SessionMessage['response'], { action: Request['action'] }>
    >(request: Request, body: Response['body']) {
        this.response(request, body)
    }

    public close(code?: number, reason?: string): void {
        this.ws.close(code, reason);
    }

    private handleMessage(event: MessageEvent) {
        const msg = JSON.parse(event.data) as AnyMessage;
        this.dispatchEvent(msg.type, msg)
    }

    private handleConnected(msg: ConnectedRequest) {
        log('链接已建立', msg)
        this.heartBeat();
        if (getToken()) {
            this._request('login', { token: getToken() })
                .then(resp => {
                    if (resp.body.errorCode === 20000) {
                        log('用户已登录', msg)
                        this.retryCount = 0;
                        this.dispatchEvent('login', {})
                    } else {
                        log('登录失败', msg)
                    }
                })
        }
    }

    private handleClose(event: CloseEvent) {
        log('⚡关闭⚡', event)
        this.dispatchEvent('close', {})
    }

    private heartBeat() {
        if (this.ws.readyState === this.ws.OPEN) {
            log('⬆心跳⬆')
            this._request('heartBeat', {})
                .then(resp => {
                    log('⬇心跳⬇', resp)
                })
                .delay(15000)
                .then(this.heartBeat.bind(this))
        }
    }
}


let sn = 100000;
function getSN() {
    sn = sn + 1;
    return sn;
}


export function log(message: any, ...optionalParams: any[]): void {
    if (SIOSession.enableLog) {
        console.log('[SIO]', message, ...optionalParams);
    }
}
